var mongoose = require('mongoose')
    Schema = mongoose.Schema;
var fs = require('fs'),
    path = require('path'),
    gm = require('gm'),
    utils = require('../utils');

const DEFAULT_A_HASH_8 = '0000000000000000';

var schema = new Schema({
    baseInfo: {
        src: String,
        name: String,
        width: {
            type: Number,
            default: 0
        },
        height: {
            type: Number,
            default: 0
        },
        aspectRatio: {
            type: Number,
            default: 0
        },
        size: {
            type: Number,
            default: 0
        },
        createDate: {
            type: Number,
            default: function() {
                return Date.now();
            }
        },
        lastModifyDate: {
            type: Number,
            default: function() {
                return Date.now();
            }
        },
        aHash_8: {
            type: String,
            default: DEFAULT_A_HASH_8
        },
        bHash_8: {
            type: String,
            default: DEFAULT_A_HASH_8
        },
        aHash_8_d: {
            type: Array,
            default: function() {
                return []
            }
        },
        aHash_8_d_md5: {
            type: String,
            default: ""
        },
        bHash_8_d: {
            type: Array,
            default: function() {
                return []
            }
        },
        bHash_8_d_md5: {
            type: String,
            default: ""
        },

    },
    extendInfo: [{
        date: {
            type: Number,
            default: function() {
                return Date.now()
            }
        },
        title: String,
        content: String,
    }]
}),
    fn = schema.methods,
    modelFn = schema.statics;


schema.pre('save', function() {
    this.baseInfo.lastModifyDate = Date.now();
    this.markModified('baseInfo');
    _buffer = null;
});

modelFn.create = function(baseInfo) {
    return new model({
        baseInfo: baseInfo,
        extendInfo: []
    }).save();
}

modelFn.clone = async function(_id) {
    var doc = await model.read(_id);
    var entity = new model({
        baseInfo: doc.baseInfo,
        extendInfo: doc.extendInfo.map(x => {
            var c = Object.assign({}, x);
            delete c._id;
            delete c.__v;
        })
    });
    return await entity.save();
}

modelFn.read = function(_id) {
    return model.findById(_id);
}

modelFn.addExtendInfo = function(_id, info) {
    info.date = Date.now();
    return model.findById(_id)
    .then(function(doc) {
        doc.extendInfo.push(info);
        doc.markModified('extendInfo');
        return doc.save();
    })
}

modelFn.updateExtendInfo = function(_id, info) {
    return model.findById(_id)
    .then(function(doc) {
        doc.extendInfo
            .filter(x => x._id == info._id)
            .forEach(x => Object.assign(x, info, {date: Date.now()}));
        doc.markModified('extendInfo');
        return doc.save();
    });
}

modelFn.removeExtendInfo = function(_id, info) {
    return model.findById(_id)
    .then(function(doc) {
        var i = doc.extendInfo.findIndex(x => x._id == info._id);
        i != -1 && doc.extendInfo.splice(i, 1);
        doc.markModified('extendInfo');
        return doc.save();
    })
}

modelFn.rename = function(_id, name) {
    return model.findById(_id)
    .then(function(doc) {
        doc.baseInfo.name = name;
        doc.markModified('baseInfo');
        return doc.save();
    })
}


modelFn.delete = function(_id, couldFileDestroy) {
    return model.findById(_id).then(doc => {
        doc.remove().then(function() {
            return model.countDocuments({})
                 .where('baseInfo.src').equals(doc.baseInfo.src)
                 .then(c => {
                    if (!c && couldFileDestroy) {
                        return couldFileDestroy(doc.baseInfo.src);
                    }
                 })
        })
    })
}


modelFn.createFromFile = async function(img, {
    name, targetDir, thumbDir, srcName
}) {
    var now = Date.now();
    var basename = path.basename(img.path),
        extname = path.extname(img.path);
        
    srcName = srcName || (`ux_${utils.uniqueString()}${extname}`);

    var src = path.join('/static/image', srcName).replace(/\\/g, '/');

    var baseInfo = {
        src,
        name,
        size: img.size,
        width: -1,
        height: -1,
        createDate: now,
        lastModifyDate: now,
        aHash_8: DEFAULT_A_HASH_8,
        bHash_8: DEFAULT_A_HASH_8
    };

    var shape = await utils.getImageSize(img.path);
    baseInfo.width = shape.width;
    baseInfo.height = shape.height;
    baseInfo.aspectRatio = shape.width / shape.height;
    baseInfo.aHash_8 = await utils.get_aHash(img.path, 8);
    baseInfo.bHash_8 = await utils.get_bHash(img.path, 8);

    baseInfo.aHash_8_d = utils.K.HASH8.arr.map(x => utils.distanceOf(x, baseInfo.aHash_8));
    baseInfo.bHash_8_d = utils.K.HASH8.arr.map(x => utils.distanceOf(x, baseInfo.bHash_8));
    baseInfo.aHash_8_d_md5 = baseInfo.bHash_8_d_md5 = utils.K.HASH8.md5;

    await utils.copyFile(img.path, path.join(targetDir, srcName));

    return await model.create(baseInfo);
}

modelFn.listAll = function() {
    return model.find({});
}

modelFn.listSome = async function(_ids) {
    var indexMap = _ids.reduce((a, b, i) => a[b] = i, {});
    var docs = await model.find({}).where('_id').in(_ids);
    docs.sort((a, b) => indexMap[a._id] - indexMap[b._id]);
    return docs;
}

modelFn.similaryImageIds = async function(_id, di, _idInsides) {
    var img = await model.findById(_id);
    di = Math.max(Math.min(di, 10), 0);
    var hashQ = Object.assign(
        {
            _id: {$ne: img._id}
        },
        utils.toQueryObj('baseInfo.aHash_8_d', img.baseInfo.aHash_8_d, di),
        utils.toQueryObj('baseInfo.bHash_8_d', img.baseInfo.bHash_8_d, di)
    );
    if (_idInsides) {
        hashQ._id.$in = _idInsides;
    }
    var rs = await model.find(hashQ);
    return rs
    .filter((x, i) => {
        return model.isSimilary(x, img, di);
    }).map(x => x._id.toString());
}

modelFn.groupBySimilary = async function(_ids, di) {
    var alone = [], rs = [], indexMap = _ids.reduce((a, b, i) => a[b] = i, {});
    var imgs = await model.find({}).where('_id').in(_ids).select('baseInfo.aHash_8 baseInfo.bHash_8');
    imgs.sort((a, b) => indexMap[a._id] - indexMap[b._id]);

    while(imgs.length) {
        var cur = imgs.shift(), similaries = [cur];
        for (var i = 0, len = imgs.length; i < len; i++) {
            var img = imgs[i];
            if (model.isSimilary(cur, img, di)) {
                similaries.push(imgs.splice(i, 1)[0]);
                i--;
                len--;
                continue;
            }
        }
        if (similaries.length > 1) {
            rs.push({
                _id: similaries.length,
                arr: similaries.map(x => x._id)
            });
        } else {
            alone.push(cur);
        }

    }
    rs.push({ _id: alone.length, arr: alone.map(x => x._id) });

    return rs;
}

modelFn.isSimilary = function(img1, img2, di) {
    return utils.isSimilary(
        [img1.baseInfo.aHash_8, img1.baseInfo.bHash_8],
        [img2.baseInfo.aHash_8, img2.baseInfo.bHash_8],
        di
    );
}

// mongoose.model('image', schema) 要做modelFn定义完后调用，否则modelFn中的自定义静态函数不会被装载上model
var model = mongoose.model('image', schema);
module.exports = model;